package controleur;

import java.awt.EventQueue;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Map.Entry;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

import sim_moteur.Terrain;
import sim_moteur.element.vegetal.Vegetal;
import util.SimProperties;
import vue.Affichable;
import vue.AppWindow;
import vue.event.InitEvent;
import vue.event.PauseEvent;
import vue.event.ReadXMLEvent;
import vue.event.SelectPeriodeEvent;
import vue.event.StartEvent;
import vue.event.StepEvent;
import vue.event.VueEvent;
import controller.xml.Commande;
import controller.xml.ControllerXML;

public class Controleur {

	private static final String SIMULATION_SORTIE_ECRAN = "simulation.sortie.ecran";
	private static final String SIMULATION_SORTIE_XML = "simulation.sortie.XML";
	private static final int PERIODE_PAS_DE_TEMPS = 1000;
	public final static int NOMBRE_DE_SIMULATEUR_DEFAUT = 1;
	public final static String VEGETAUX_PROPERTY_NAME = "simulation.vegetaux.nombre";
	public final static String ANIMAUX_PROPERTY_NAME = "simulation.animal.nombre";
	public final static String CHOSE_PROPERTY_NAME = "simulation.chose.nombre";
	public final static String DEFAULT_SCENARIO_NAME = "sim";

	private ControllerXML controllerXML;
	private String scenarioName = "scenario 2";
	private boolean afficheEcran;
	private boolean sortieXML;

	private int nombreDeSimulateur;

	private AppWindow vue;
	// Simulateur sim;// c'est le modèle
	private SimulationControllerThread[] simControllerList;
	private RendezVous stepRendezVous;

	public Controleur() {
		super();

		// On synchronise la durée de vie max des plantes
		Affichable.setVieMax(Vegetal.VIE_MAX);

		Terrain.getInstance().init();
		int largeur = Terrain.getInstance().getXSize();
		int hauteur = Terrain.getInstance().getYSize();
		vue = new AppWindow(largeur, hauteur);
		controllerXML = new ControllerXML(this);

		vue.addObserver((o, arg) -> {
			VueEvent event = (VueEvent) arg;
			if (event instanceof StepEvent) {
				stepSimulation();
			} else if (event instanceof StartEvent) {
				startSimulation();
			} else if (event instanceof InitEvent) {
				initSimulation();
			} else if (event instanceof PauseEvent) {
				// On bloque le rendez-vous
				stepRendezVous.pause();
			} else if (event instanceof ReadXMLEvent) {
				// On bloque le rendez-vous
				readXML();
			} else if (event instanceof SelectPeriodeEvent) {
				SelectPeriodeEvent periodeEvent = (SelectPeriodeEvent) event;
				for (SimulationControllerThread simCtrl : simControllerList) {
					simCtrl.setPeriode(periodeEvent.getPeriode());
				}
			}
		});
		vue.initialize();
		init(NOMBRE_DE_SIMULATEUR_DEFAUT, DEFAULT_SCENARIO_NAME);
	}

	public void init(int nombreDeSimulateur, String scenarioName) {

		sortieXML = SimProperties.getInstance().getBooleanProperty(
				SIMULATION_SORTIE_XML);
		afficheEcran = SimProperties.getInstance().getBooleanProperty(
				SIMULATION_SORTIE_ECRAN);

		Terrain.getInstance().init();
		int largeur = Terrain.getInstance().getXSize();
		int hauteur = Terrain.getInstance().getYSize();

		this.nombreDeSimulateur = nombreDeSimulateur;
		simControllerList = new SimulationControllerThread[nombreDeSimulateur];

		stepRendezVous = new RendezVous(nombreDeSimulateur, () -> {
			try {
				EventQueue.invokeAndWait(() -> {

					if (afficheEcran) {
						vue.rafraichitVue(stepRendezVous.getStepCount());
					}
				});
				this.writeXML();
			} catch (InterruptedException | InvocationTargetException e) {
				e.printStackTrace();
			}
		});

		vue.resize(largeur, hauteur);
		vue.effaceFournisseurs();

		for (int i = 0; i < simControllerList.length; i++) {
			simControllerList[i] = new SimulationControllerThread(vue,
					stepRendezVous, PERIODE_PAS_DE_TEMPS, scenarioName + i);
			vue.setFournisseur(simControllerList[i]);
		}
		initSimulation();
	}

	public static void main(String[] args) {
		new Controleur().start();
	}

	/**
	 * Réinitialise la simulation. Remet la simulation dans l'état initiale
	 * prévu par les fichiers de propriétés.
	 */
	public void initSimulation() {

		controllerXML.init(scenarioName, nombreDeSimulateur);
		Terrain.getInstance().clear();
		// On crée un nouveau simulateur au besoin
		Terrain.getInstance().clear();
		for (int i = 0; i < simControllerList.length; i++) {
			simControllerList[i].init();
		}
		stepRendezVous.init(nombreDeSimulateur);

		long stepTemp = stepRendezVous.avancePasDeTemps();
		// on met la vue à jour
		if (afficheEcran) {
			EventQueue.invokeLater(() -> {
				vue.initSimulation();
				vue.rafraichitVue(stepTemp);
			});

		}

		this.writeXML();

		for (SimulationControllerThread simController : simControllerList) {
			synchronized (simController) {
				simController.notifyAll();
			}
		}
	}

	/**
	 * Débute la simulation. Inactive tant que les threads ne sont pas utilisés
	 */
	private void startSimulation() {
		if (stepRendezVous.isPaused()) {
			try {

				stepRendezVous.restart();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			for (SimulationControllerThread simCtrl : simControllerList) {
				simCtrl.start();
			}
		}
	}

	/**
	 * Avance la simulation d'un pas de temps
	 * 
	 * @throws XMLStreamException
	 * @throws FileNotFoundException
	 */
	private void stepSimulation() {

		for (SimulationControllerThread simCtrl : simControllerList) {
			simCtrl.step();
		}

		long stepTemp = stepRendezVous.avancePasDeTemps();
		if (afficheEcran) {
			EventQueue.invokeLater(() -> {
				vue.rafraichitVue(stepTemp);
			});

		}

		this.writeXML();

	}

	private void readXML() {
		// Vous devez lire le fichier de commande xml ici!

		InputStream input = null;

		try {
			input = new FileInputStream("./input/commande_test2.xml");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		XMLInputFactory fabrique = XMLInputFactory.newInstance();
		XMLStreamReader reader = null;

		try {
			reader = fabrique.createXMLStreamReader(input);
		} catch (XMLStreamException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		fabrique.setXMLReporter((message, typeErreur, source, location) -> {
			System.out.println("Erreur de type: " + typeErreur + ", message : "
					+ message);
		});
		Commande commande = null;
		String dernierTexte = null;
		try {
			while (reader.hasNext()) {
				int event = reader.next();
				switch (event) {
				case XMLStreamConstants.START_ELEMENT:
					switch (reader.getLocalName()) {
					case "commande":
						commande = new Commande();

						commande.setCommande(scenarioName + "."
								+ "max_pas_de_temps",
								reader.getAttributeValue(0));

						break;
					case "vivant":
						commande.setCommande(scenarioName + "0" + "."
								+ "simulation.animal.nombre",
								reader.getAttributeValue(0));

						commande.setCommande(scenarioName + "0" + "."
								+ "simulation.vegetaux.nombre",
								reader.getAttributeValue(1));

						break;
					case "chose":
						commande.setCommande(scenarioName + "0" + "."
								+ "simulation.chose.nombre",
								reader.getAttributeValue(0));
						break;
					}
					break;
				case XMLStreamConstants.END_ELEMENT:
					switch (reader.getLocalName()) {
					case "commande":
						SimProperties.getInstance().setCommande(
								commande.convertToProps());
						System.out
								.println(commande.convertToProps().toString());
						break;
					case "x":
						// TODO gérer les dimensions non carré
						commande.setCommande("simulation.terrain.x.dimension",
								dernierTexte);
						break;
					case "y":
						commande.setCommande("simulation.terrain.y.dimension",
								dernierTexte);
						break;
					}
					break;
				case XMLStreamConstants.CHARACTERS:
					dernierTexte = reader.getText().trim();
					break;
				}
			}
		} catch (XMLStreamException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		this.init(1, scenarioName);
	}

	public void writeXML() {
		if (sortieXML)
			try {
				controllerXML.writeXML(stepRendezVous.getStepCount(),
						simControllerList[0]);
			} catch (FileNotFoundException | XMLStreamException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

	/**
	 * Démarre l'application.
	 */
	private void start() {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					vue.montreVue();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
}
